package com.zmm.study.order.service.impl;

import com.zmm.common.base.model.ResultCode;
import com.zmm.common.enums.OrderStatusEnum;
import com.zmm.common.enums.RecordType;
import com.zmm.common.exception.handle.BusinessException;
import com.zmm.common.exception.handle.NoStockException;
import com.zmm.study.order.dao.CartDao;
import com.zmm.study.order.dao.OrderDao;
import com.zmm.study.order.dao.WareOrderTaskDao;
import com.zmm.study.order.dao.WareOrderTaskDetailDao;
import com.zmm.study.order.dao.WareSkuDao;
import com.zmm.study.order.model.MoNiOrder;
import com.zmm.study.order.model.Order;
import com.zmm.study.order.model.WareOrderTask;
import com.zmm.study.order.model.WareOrderTaskDetail;
import com.zmm.study.order.service.OrderService;
import com.zmm.study.order.vo.OrderCloseVo;
import com.zmm.study.order.vo.OrderItemVo;
import com.zmm.study.order.vo.SkuIdWareIdVo;
import com.zmm.study.order.ware.SkuWareHasStock;
import com.zmm.study.order.ware.StockDetailTo;
import com.zmm.study.order.ware.StockLockedTo;
import com.zmm.study.order.ware.WareSkuLockVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @Description:
 * @Name OrderServiceImpl
 * @Author Zebulon
 * @Date By 2021-07-21 23:17:11
 */
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {

    @Resource
    private CartDao cartDao;

    @Resource
    private OrderDao orderDao;
    
    @Resource
    private WareSkuDao wareSkuDao;
    
    @Resource
    private WareOrderTaskDao wareOrderTaskDao;
    
    @Resource
    private WareOrderTaskDetailDao wareOrderTaskDetailDao;

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Override
    public boolean addOrder(Order order) throws BusinessException {
        Integer insert = orderDao.insertOrder(order);
        if (insert > 0){
            return true;
        }
        throw new BusinessException(ResultCode.INSERT_FAILED, RecordType.SAVE_ORDER,"addOrder");
    }

    @Override
    public void closeOrder(MoNiOrder moNiOrder) {
        // 1.查询当前订单的最新状态
        Integer orderStatus = orderDao.getOrderStatusByOrderNo(moNiOrder.getOrderNo());
        if (OrderStatusEnum.CREATE_NEW.getCode().equals(orderStatus)){
            // 2.进行关单
            orderDao.updateOrderByOrderNo(OrderStatusEnum.CANCELED.getCode(),moNiOrder.getOrderNo());

            OrderCloseVo orderCloseVo = new OrderCloseVo();
            orderCloseVo.setOrderNo(moNiOrder.getOrderNo());
            // 2.判断当前订单的状态是否为待付款 是进行下一步 否(支付成功)无操作
            // 2.1 进行关单 --> 改变订单的状态 为 已取消
            // 2.2 订单解锁成功后 再发一个消息 给 mq 
            try {
                rabbitTemplate.convertAndSend("order-event-exchange", "order.release.other", orderCloseVo);
            } catch (Exception e){
                // 出现问题 将没有发送成功的消息进行重试发送
                //while(true)
            }
        }
        log.info("当前订单为{}状态,无需发送消息给 MQ",OrderStatusEnum.getStatusEnum(orderStatus));
       
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void closeOrder(String orderNo) {
        // 1.查询当前订单的最新状态
        Integer orderStatus = orderDao.getOrderStatusByOrderNo(orderNo);
        if (OrderStatusEnum.CREATE_NEW.getCode().equals(orderStatus)){
            // 2.进行关单
            orderDao.updateOrderByOrderNo(OrderStatusEnum.CANCELED.getCode(),orderNo);
        } else {
            log.error("此订单{}不支持关单操作",orderNo);
        }
        
    }

    @Override
    public void finishOrder(String orderNo) {
        // 1.查询当前订单的最新状态
        Integer orderStatus = orderDao.getOrderStatusByOrderNo(orderNo);
        if (OrderStatusEnum.CREATE_NEW.getCode().equals(orderStatus)){
            // 2.订单状态 已付款
            orderDao.updateOrderByOrderNo(OrderStatusEnum.PAYED.getCode(),orderNo);
        } else {
            log.error("此订单{}不支持关单操作",orderNo);
        }
    }

    /**
     * 一般使用 消息的模式 ===> 可靠消息+最终一致性
     * @author: 900045
     * @date: 2021-10-20 11:14:37
     * @throws 
     * @param moNiOrder: 
     * @return: void
     **/
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void submitOrder(MoNiOrder moNiOrder) {
        // 1.获取当前用户
        // 2.验证订单的防重令牌 --> Lua 脚本
        // 3.创建订单 --> 3.1 构建订单实体  3.2 获取到所有的的订单项 3.3 计算价格、积分相关信息
        orderDao.insertMoNiOrder(moNiOrder);
        WareSkuLockVo wareSkuLockVo = new WareSkuLockVo();
        wareSkuLockVo.setOrderId(moNiOrder.getOrderId());
        wareSkuLockVo.setOrderNo(moNiOrder.getOrderNo());
        List<OrderItemVo> orderItemVos = cartDao.findCartByUserId(1);
        wareSkuLockVo.setLocks(orderItemVos);
        orderLockStock(wareSkuLockVo);
        // 4.校验价格
        // 5.保存订单 Question1.出现异常 订单回滚 √
        // 6.锁库存得到结果 库存服务 R r = wareFeignService.orderLockStock(skuLockVo) 锁定库存
        /**
         * Transactional --> 在分布式系统,只能控制住自己的回滚 控制不了其他服务的回滚
         * 存在的问题:
         *      1. 本身服务出现异常 需要回滚
         *         本质: Transactional √
         *      2. 远程锁库存成功 但是因为网络原因(超时导致) 导致出现异常 从而影响 订单创建回滚
         *        本质: 库存锁成功 但是订单回滚了   (远程服务假失败 真是的库存锁成功) √
         *      3. 远程锁库存成功 但是因为调用其他服务出现异常 已执行的远程请求,肯定不能回滚
         *        本质: 库存锁成功 除订单服务外的 其它服务出现异常
         * 解决的方案:
         *      应该使用 分布式事务 ===> 最大原因 网络问题 + 分布式机器
         */
        
        // 7.判断远程执行的结果
        boolean flag = true;
        if (flag){
            // 结果: 锁定库存成功(库存成功,但是调用其它的服务失败,导致订单回滚,所以库存也需要回滚)
            // 说明: 远程执行其它服务失败 --> 订单可以回滚 √ 但是远程执行的锁库存已经执行成功(无法执行回滚)[订单回滚,库存不回滚]
            // 方案: 发送消息 --> 需要执行回滚 -->解锁刚才的库存
            // 创建订单队列 -- 延时队列 【CREATE_ORDER】
            rabbitTemplate.convertAndSend("order-event-exchange","order.create.order",moNiOrder);
        } else {
            // 结果: 锁定库存失败 (真异常(订单回滚,库存需要回滚)/假异常(其实是执行成功,但是返回的是执行失败,导致订单回滚,所以库存也需要回滚))
            // 说明: 远程服务本身调用异常  --> 远程服务没有执行成功(无需回滚); 订单服务 由于前面已经创建订单,所以需要回滚(直接抛出异常) 
            // 方案: 异常机制-- 感知异常进行回滚
            throw new NoStockException(3L, ResultCode.DISCOUNT_STOCK_EXCEPTION);
        }
        // 没有异常 1.订单执行成功 2.库存执行成功 3.远程其它服务执行成功
        
    }

    @Transactional(rollbackFor = NoStockException.class)
    @Override
    public boolean orderLockStock(WareSkuLockVo wareSkuLockVo) {
        // 1.保存库存工作单的详情
        WareOrderTask wareOrderTask = new WareOrderTask();
        wareOrderTask.setOrderId(wareSkuLockVo.getOrderId());
        wareOrderTask.setOrderNo(wareSkuLockVo.getOrderNo());
        wareOrderTask.setCreateTime(new Date());
        wareOrderTaskDao.insertWareOrderTask(wareOrderTask);
        // 2.按照下单的收货地址, 找到一个就近仓库, 锁定库存
        List<OrderItemVo> locks = wareSkuLockVo.getLocks();
        List<Long> skuIdList = locks.stream().map(OrderItemVo::getSkuId).collect(Collectors.toList());
        List<SkuIdWareIdVo> wareIdHasSkuStock = wareSkuDao.findWareIdHasSkuStock(skuIdList);
        Map<Long, List<SkuIdWareIdVo>> map = wareIdHasSkuStock.stream().collect(Collectors.groupingBy(SkuIdWareIdVo::getSkuId));
        
        List<SkuWareHasStock> skuWareHasStocks = locks.stream().map(item->{
            SkuWareHasStock skuWareHasStock = new SkuWareHasStock();
            Long skuId = item.getSkuId();
            skuWareHasStock.setSkuId(skuId);
            skuWareHasStock.setNum(item.getCount());
            List<SkuIdWareIdVo> skuIdWareIdVos = map.get(skuId);
            List<Long> wareIds = skuIdWareIdVos.stream().map(SkuIdWareIdVo::getWareId).collect(Collectors.toList());
            skuWareHasStock.setWareId(wareIds);
            return skuWareHasStock;
        }).collect(Collectors.toList());
        // 3.锁定库存 skuWareHasStocks
        for (SkuWareHasStock skuWareHasStock:skuWareHasStocks) {
            boolean skuStocked = false;
            Long skuId = skuWareHasStock.getSkuId();
            List<Long> wareIds = skuWareHasStock.getWareId();
            if (CollectionUtils.isEmpty(wareIds)){
                // 没有任何仓库有这个商品
                throw new NoStockException(skuId,ResultCode.WARE_NULL_POINT_EXCEPTION);
            }
            // 存在多个库存
            for (Long wareId:wareIds) {
                Integer count = wareSkuDao.lockSkuStock(skuId,wareId,skuWareHasStock.getNum());
                if (count > 0 ){
                    skuStocked = true;
                    WareOrderTaskDetail wareOrderTaskDetail = new WareOrderTaskDetail(null,skuId,skuWareHasStock.getNum(),
                            wareOrderTask.getId(),wareId,
                            1);
                    wareOrderTaskDetailDao.insertWareOrderTaskDetail(wareOrderTaskDetail);

                    StockLockedTo stockLockedTo = new StockLockedTo();
                    stockLockedTo.setTaskId(wareOrderTask.getId());
                    StockDetailTo stockDetailTo = new StockDetailTo();
                    BeanUtils.copyProperties(wareOrderTaskDetail,stockDetailTo);

                    // 商品号 - 数量 - 库存
                    // 1: 1 - 2 - 1 2: 2 - 1 - 2  3: 3 - 1 - 1 (失败)
                    stockLockedTo.setStockDetail(stockDetailTo);
                    // 锁定库存 MQ 【LOCK_STOCK】
                    rabbitTemplate.convertAndSend("stock-event-exchange","stock.locked",stockLockedTo);
                    break;
                } else {
                    // 当前仓库锁失败 重试下一个仓库
                }
            }
            if (skuStocked == false) {
                // 当前商品所有库存都没有锁成功
                throw new NoStockException(skuId,ResultCode.LOCK_STOCK_EXCEPTION);
            }
        }
        // 3.1 判断该商品在仓库中是否存在 只要有一个没有 抛出异常
        // 3.2 锁定成功 (每一个商品都锁定成功) 将当前商品锁定了几件的工作单记录发送给 MQ
        // 3.2 锁定失败 (存在一个商品锁定失败) 前面保存的工作单会回滚(不过可能会存在有消息已经发出去了) [发出去的工作单,已经回滚在数据中查询不到]

        // 没有执行异常就相当于锁定成功
        return true;
    }


    /**
     * 
     * 逻辑  ---> 解锁
     *     1.查询数据库关于这个订单的锁定库存信息
     *          有: 证明 库存时锁定成了 但是 到底要不要解锁 --> 还得查询订单详情
     *              1.没有这个订单 --> 必须解锁
     *              2.有这个订单 --> 是不是解锁库存  --> 还得查询订单状态
     *                  订单状态:
     *                      1.已取消:解锁库存
     *                      2.没取消:不能解锁
     *                       
     *         没有:库存锁定失败了 库存回滚了. 这种情况无需解锁
     * @author: 900045
     * @date: 2021-10-20 17:16:59
     * @throws 
     * @param to: 
     * @return: void
     **/
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void unLockStock(StockLockedTo to) {
        // 方法只要一切正常运行 就是库存解锁成功
        log.error("解锁库存");

        Long taskId = to.getTaskId();
        StockDetailTo stockDetail = to.getStockDetail();
        Long detailId = stockDetail.getId();
        log.info("stockDetail:{}",stockDetail.toString());

        WareOrderTaskDetail orderTaskDetail = wareOrderTaskDetailDao.getWareOrderTaskDetailByDetailId(detailId);
        if (ObjectUtils.isEmpty(orderTaskDetail)){
            // 无需解锁 
        } else {
            // 需要解锁
            WareOrderTask orderTask = wareOrderTaskDao.getWareOrderTaskByTaskId(taskId);

            // 根据订单号查询订单的状态

            String orderNo = orderTask.getOrderNo();
            
            // 远程查询订单服务 此处就不了
            Integer orderStatus = orderDao.getOrderStatusByOrderNo(orderNo);
            if (OrderStatusEnum.CANCELED.getCode().equals(orderStatus)){
                // 订单不存在 才可以解锁库存
                // 订单已经被取消了 ,才可以解锁库存
                if (orderTaskDetail.getLockStatus() == 1) {
                    // 当前库存工作单详情 状态为 1(未解锁) 才可以解锁
                    log.info("工作详单解锁");
                    unLockStock(stockDetail.getSkuId(), stockDetail.getWareId(), stockDetail.getSkuNum(), detailId);
                } else {
                    log.info("库存工作单详情的状态为{}",orderTaskDetail.getLockStatus() == 2?"已解锁":"扣减");
                }
            }
            // 远程服务调用失败
            //throw new CustomRunTimeException(ResultCode.REMOTE_SERVICE_FAIL);
        }
    }

    @Override
    public void unLockStock(String orderNo,Long skuId,Integer skuNum,Long wareId) {
        WareOrderTask wareOrderTask = wareOrderTaskDao.getOrderTaskByOrderSn(orderNo);
        WareOrderTaskDetail wareOrderTaskDetail = new WareOrderTaskDetail();
        wareOrderTaskDetail.setTaskId(wareOrderTask.getId());
        wareOrderTaskDetail.setSkuId(skuId);
        wareOrderTaskDetail.setSkuNum(skuNum);
        wareOrderTaskDetail.setWareId(wareId);
        wareOrderTaskDetail.setLockStatus(2);
        wareOrderTaskDetailDao.updateTaskDetailByDetail(wareOrderTaskDetail);
    }

    /**
     * 手动解锁 -- 全部
     * @author: 900045
     * @date: 2021-10-21 14:27:33
     * @throws 
     * @param orderNo: 
     * @return: void
     **/
    @Override
    public void unLockStock(String orderNo) {
        // 1.解锁工作单
        WareOrderTask wareOrderTask = wareOrderTaskDao.getOrderTaskByOrderSn(orderNo);
        List<WareOrderTaskDetail> wareOrderTaskDetailList = wareOrderTaskDetailDao.findOrderTaskDetailListByTaskId(wareOrderTask.getId(),
                1);
        wareOrderTaskDetailDao.updateTaskDetailByTaskId(wareOrderTask.getId(),2);
        // 2.退款库存
        
        if (!CollectionUtils.isEmpty(wareOrderTaskDetailList)) {
            wareSkuDao.batchUnLockStock(wareOrderTaskDetailList);
        }
    }

    @Override
    public void discountStock(String orderNo) {
        // 1.库存工作单详情 真实扣减
        WareOrderTask wareOrderTask = wareOrderTaskDao.getOrderTaskByOrderSn(orderNo);
        wareOrderTaskDetailDao.updateTaskDetailByTaskId(wareOrderTask.getId(),3);
    }

    /**
     * 解锁库存的方法 -- 消息使用 (单个解锁)
     * @author: 900045
     * @date: 2021-10-20 17:39:35
     * @throws 
     * @param skuId: 
     * @param wareId: 
     * @param num: 
     * @param taskDetailId: 
     * @return: void
     **/
    private void unLockStock(Long skuId,Long wareId,Integer num,Long taskDetailId){
        // 1.库存解锁 wms_ware_sku 表 减回原来的数量 
        wareSkuDao.unLockStock(skuId,wareId,num);
        // 2.更新库存工作单的状态 变为已解锁
        wareOrderTaskDetailDao.updateTaskDetailById(taskDetailId,2);
    }
}
